<!--
Copyright 2019 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<!doctype html>
<head>
 <title>piex wasm raw image preview / thumbnail test page</title>
 <link type="text/css" rel="stylesheet" href="/tests.css">
</head>

<body>
  <button onclick=runTest()>Run Test</button>
</body>

<script>
  class ImageBuffer {
    constructor(buffer) {
      this.source = new Uint8Array(buffer);
      this.length = buffer.byteLength;
    }

    process() {
      this.memory = Module._malloc(this.length);
      if (!this.memory)
        throw new Error('image malloc failure');

      Module.HEAP8.set(this.source, this.memory);
      this.result = Module.image(this.memory, this.length);

      return this.result;
    }

    preview() {
      let preview = this.result ? this.result.preview : null;
      if (!preview || this.result.error)
        return null;

      if (preview.format != 0)
        throw new Error('preview images should be JPEG format');

      const offset = preview.offset;
      const length = preview.length;
      if (offset > this.length || (this.length - offset) < length)
        throw new Error('failed to extract preview');

      const view = new Uint8Array(this.source.buffer, offset, length);
      preview.data = new Uint8Array(view);
      preview.type = 'preview';

      return preview;
    }

    thumbnail() {
      let thumbnail = this.result ? this.result.thumbnail : null;
      if (!thumbnail || this.result.error)
        return null;

      const offset = thumbnail.offset;
      const length = thumbnail.length;
      if (offset > this.length || (this.length - offset) < length)
        throw new Error('failed to extract thumbnail');

      const view = new Uint8Array(this.source.buffer, offset, length);
      thumbnail.data = new Uint8Array(view);
      if (thumbnail.format == 1)  // RGB
        thumbnail.size = thumbnail.width * thumbnail.height * 3;
      thumbnail.type = 'thumbnail';

      return thumbnail;
    }

    details() {
      const details = this.result ? this.result.details : null;
      if (!details || this.result.error)
        return null;

      let format = {};
      for (const [key, value] of Object.entries(details)) {
        if (typeof value === 'string') {
          format[key] = value.replace(/\0+$/, '').trim();
        } else if (typeof value === 'number') {
          if (!Number.isInteger(value)) {
            format[key] = Number(value.toFixed(3).replace(/0+$/, ''));
          } else {
            format[key] = value;
          }
        }
      }

      return JSON.stringify(format);
    }

    close() {
      Module._free(this.memory);
    }
  }

  function createFileSystem(images) {
    return new Promise((resolve, reject) => {
      document.title = 'createFileSystem';

      function failed(error) {
        reject(new Error('Creating file system: ' + error));
      }

      function createdFileSystem(fileSystem) {
        console.log('test: created file system', fileSystem.name);
        window.fileSystem = fileSystem;
        resolve();
      }

      const bytes = images * 30 * 1024 * 1024;  // 30M per image.
      window.webkitRequestFileSystem(
          window.TEMPORARY, bytes, createdFileSystem, failed);
    });
  }

  function writeToFileSystem(image) {
    return new Promise(async (resolve, reject) => {
      document.title = image;

      const buffer = await fetch(image).then((response) => {
        if (!response.ok)
          throw new Error('Failed to fetch image: ' + image);
        return response.arrayBuffer();
      }).catch(reject);

      function failure(error) {
        reject(new Error('Writing file system: ' + error));
      }

      function writeEntry(fileEntry) {
        fileEntry.createWriter((writer) => {
          writer.onerror = failure;
          writer.onwrite = resolve;
          writer.write(new Blob([buffer]));
        }, failure);
      }

      window.fileSystem.root.getFile(
          image.replace('images/', ''), {create: true}, writeEntry, failure);
    });
  }

  function readFromFileSystem(image) {
    return new Promise((resolve, reject) => {
      document.title = image;

      function failure(error) {
        reject(new Error('Reading file system: ' + error));
      }

      function invalid(size) {
        return size <= 0 || size >= Math.pow(2, 30);
      }

      function readEntry(fileEntry) {
        fileEntry.file((file) => {
          if (invalid(file.size))
            return failure('invalid file size');
          const reader = new FileReader();
          reader.onerror = failure;
          reader.onload = () => resolve(reader.result);
          reader.readAsArrayBuffer(file);
        }, failure);
      }

      window.fileSystem.root.getFile(
          image.replace('images/', ''), {}, readEntry, failure);
    });
  }

  function hashUint8Array(data, hash = ~0) {
    for (let i = 0; i < data.byteLength; ++i)
      hash = (hash << 5) - hash + data[i];
    return Math.abs(hash).toString(16);
  }

  function renderJPG(name, image) {
    const data = image.data;

    let renderer = new Image();
    renderer.onerror = renderer.onload = (event) => {
      if (renderer.width > (window.screen.availWidth / 2))
        renderer.classList.add('zoom');
      document.body.appendChild(renderer);
      URL.revokeObjectURL(renderer.src);
      if (--window.images_ <= 0)
        document.title = 'DONE';
    };

    renderer.src = URL.createObjectURL(new Blob([data]));
    ++window.images_;
  }

  function renderRGB(name, image) {
    const data = image.data;

    let canvas = document.createElement('canvas');
    canvas.width = image.width;
    canvas.height = image.height;
    if (image.width > (window.screen.availWidth / 2))
      canvas.classList.add('zoom');

    // Create imageData from the image RGB data.
    let context = canvas.getContext('2d');
    let imageData = context.createImageData(image.width, image.height);
    for (let i = 0, j = 0; i < image.size; i += 3, j += 4) {
      imageData.data[j + 0] = data[i + 0]; // R
      imageData.data[j + 1] = data[i + 1]; // G
      imageData.data[j + 2] = data[i + 2]; // B
      imageData.data[j + 3] = 255;         // A
    }

    // Render the imageData.
    context.putImageData(imageData, 0, 0);
    document.body.appendChild(canvas);
  }

  function renderResult(name, image) {
    if (!image)
      return;

    const hash = hashUint8Array(image.data);
    const data = image.data;
    image.data = undefined;
    const result = JSON.stringify(image);
    console.log('test:', name, image.type, 'hash', hash, result);
    image.data = data;

    if (image.format == 0)
      return renderJPG(name, image);
    if (image.format == 1)
      return renderRGB(name, image);
  }

  function renderDetails(name, details) {
    if (!details)
      return;

    const text = new TextEncoder('UTF-8').encode(details);
    const hash = hashUint8Array(text);

    console.log('test:', name, 'details hash', hash, details);
  }

  window.onload = function loadPiexModule() {
    let script = document.createElement('script');
    document.head.appendChild(script);
    script.src = '/piex.js.wasm';
    script.onload = () => {
      createPiexModule().then(module => {
        window.Module = module;
        document.title = 'READY';
      });
    };
  };

  window.onerror = (error) => {
    console.log('test: FAIL', error, '\n');
    document.title = 'DONE';
  };

  async function runTest(image = 'images/SONY_A500_01.ARW') {
    // Start the test of image.
    document.title = image;
    console.log('test:', image);
    document.body.innerHTML = `<pre>${image}</pre>`;

    // Fetch the image in an array buffer.
    let time = window.performance.now();
    const buffer = await readFromFileSystem(image).catch(onerror);
    if (!buffer)
      return;

    let imageBuffer = new ImageBuffer(buffer);
    const fetched = window.performance.now() - time;

    // Extract the preview|thumbnail images, render them.
    return new Promise((resolve) => {
      resolve(imageBuffer.process());
    }).then((result) => {
      let preview = imageBuffer.preview();
      let thumb = imageBuffer.thumbnail();
      let details = imageBuffer.details();
      imageBuffer.close();
      time = window.performance.now() - time;
      window.images_ = 0;
      renderDetails(image, details);
      renderResult(image, preview);
      renderResult(image, thumb);
      console.log('test: done',
        time.toFixed(3), fetched.toFixed(3), (time - fetched).toFixed(3));
      window.testTime += time;
      console.log('\n');
      if (!window.images_)
        document.title = 'DONE';
    }).catch((error) => {
      imageBuffer.close();
      console.log(error);
      document.title = 'DONE';
    });
  }
</script>
